Skip to contentMethod: buildCloneImpl(Object, Field, Object, CloneConfiguration)
1: /**
2: * Copyright (C) 2016 Czech Technical University in Prague
3: *
4: * This program is free software: you can redistribute it and/or modify it under
5: * the terms of the GNU General Public License as published by the Free Software
6: * Foundation, either version 3 of the License, or (at your option) any
7: * later version.
8: *
9: * This program is distributed in the hope that it will be useful, but WITHOUT
10: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11: * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12: * details. You should have received a copy of the GNU General Public License
13: * along with this program. If not, see <http://www.gnu.org/licenses/>.
14: */
15: package cz.cvut.kbss.jopa.sessions;
16:
17: import cz.cvut.kbss.jopa.adapters.IndirectCollection;
18: import cz.cvut.kbss.jopa.exceptions.OWLPersistenceException;
19: import cz.cvut.kbss.jopa.model.annotations.Inferred;
20: import cz.cvut.kbss.jopa.model.descriptors.Descriptor;
21: import cz.cvut.kbss.jopa.model.metamodel.EntityType;
22: import cz.cvut.kbss.jopa.model.metamodel.FieldSpecification;
23: import cz.cvut.kbss.jopa.model.metamodel.Metamodel;
24: import cz.cvut.kbss.jopa.utils.EntityPropertiesUtils;
25: import org.slf4j.Logger;
26: import org.slf4j.LoggerFactory;
27:
28: import java.lang.reflect.Field;
29: import java.net.URI;
30: import java.net.URL;
31: import java.util.*;
32:
33: public class CloneBuilderImpl implements CloneBuilder {
34:
35: private static final Logger LOG = LoggerFactory.getLogger(CloneBuilderImpl.class);
36:
37: private static final Set<Class<?>> IMMUTABLE_TYPES = getImmutableTypes();
38:
39: // Contains entities that are already cloned, so that we don't clone them again
40: private final RepositoryMap visitedEntities;
41:
42: private final Builders builders;
43:
44: private final UnitOfWorkImpl uow;
45:
46: public CloneBuilderImpl(UnitOfWorkImpl uow) {
47: this.uow = uow;
48: this.visitedEntities = new RepositoryMap();
49: this.builders = new Builders();
50: }
51:
52: @Override
53: public Object buildClone(Object original, CloneConfiguration cloneConfiguration) {
54: Objects.requireNonNull(original);
55: Objects.requireNonNull(cloneConfiguration);
56: LOG.trace("Cloning object {}.", original);
57: return buildCloneImpl(null, null, original, cloneConfiguration);
58: }
59:
60: @Override
61: public Object buildClone(Object cloneOwner, Field clonedField, Object original, Descriptor descriptor) {
62: if (cloneOwner == null || original == null || descriptor == null) {
63: throw new NullPointerException();
64: }
65: LOG.trace("Cloning object {} with owner {}", original, cloneOwner);
66: return buildCloneImpl(cloneOwner, clonedField, original, new CloneConfiguration(descriptor));
67: }
68:
69: private Object buildCloneImpl(Object cloneOwner, Field clonedField, Object original,
70: CloneConfiguration cloneConfiguration) {
71:• if (isOriginalInUoW(original)) {
72: return uow.getCloneForOriginal(original);
73: }
74: final Class<?> cls = original.getClass();
75: final boolean managed = isTypeManaged(cls);
76: final Descriptor descriptor = cloneConfiguration.getDescriptor();
77:• if (managed) {
78: final Object visitedClone = getVisitedEntity(descriptor, original);
79:• if (visitedClone != null) {
80: return visitedClone;
81: }
82: }
83: final AbstractInstanceBuilder builder = getInstanceBuilder(original);
84: Object clone = builder.buildClone(cloneOwner, clonedField, original, cloneConfiguration);
85:• if (managed) {
86: // Register visited object before populating attributes to prevent endless cloning cycles
87: putVisitedEntity(descriptor, original, clone);
88: }
89:• if (!builder.populatesAttributes() && !isImmutable(original.getClass())) {
90: populateAttributes(original, clone, cloneConfiguration);
91: }
92: return clone;
93: }
94:
95: /**
96: * Clone all the attributes of the original and set the clone values. This also means cloning any relationships and
97: * their targets.
98: */
99: private void populateAttributes(Object original, Object clone, CloneConfiguration configuration) {
100: final Class<?> originalClass = original.getClass();
101: final EntityType<?> et = getMetamodel().entity(originalClass);
102: for (FieldSpecification<?, ?> fs : et.getFieldSpecifications()) {
103: final Field f = fs.getJavaField();
104: final Object origVal = EntityPropertiesUtils.getFieldValue(f, original);
105: if (origVal == null) {
106: continue;
107: }
108: final Class<?> origValueClass = origVal.getClass();
109: Object clonedValue;
110: if (isImmutable(origValueClass)) {
111: // The field is an immutable type
112: clonedValue = origVal;
113: } else if (origVal instanceof Collection || origVal instanceof Map) {
114: final Descriptor fieldDescriptor = getFieldDescriptor(f, originalClass, configuration.getDescriptor());
115: // Collection or Map
116: clonedValue = getInstanceBuilder(origVal).buildClone(clone, f, origVal,
117: new CloneConfiguration(fieldDescriptor, configuration.getPostRegister()));
118: } else {
119: // Otherwise we have a relationship and we need to clone its target as well
120: if (isOriginalInUoW(origVal)) {
121: // If the reference is already managed
122: clonedValue = uow.getCloneForOriginal(origVal);
123: } else {
124: if (isTypeManaged(origValueClass)) {
125: final Descriptor fieldDescriptor =
126: getFieldDescriptor(f, originalClass, configuration.getDescriptor());
127: clonedValue = getVisitedEntity(configuration.getDescriptor(), origVal);
128: if (clonedValue == null) {
129: clonedValue = uow.registerExistingObject(origVal, fieldDescriptor,
130: configuration.getPostRegister());
131: }
132: } else {
133: clonedValue = buildClone(origVal, configuration);
134: }
135: }
136: }
137: EntityPropertiesUtils.setFieldValue(f, clone, clonedValue);
138: }
139: }
140:
141: private Descriptor getFieldDescriptor(Field field, Class<?> entityClass, Descriptor entityDescriptor) {
142: final EntityType<?> et = getMetamodel().entity(entityClass);
143: final FieldSpecification<?, ?> fieldSpec = et.getFieldSpecification(field.getName());
144: return entityDescriptor.getAttributeDescriptor(fieldSpec);
145: }
146:
147: /**
148: * Check if the given class is an immutable type. This is used by the {@link
149: * #populateAttributes(Object, Object, CloneConfiguration)} method. If this returns true, the populateAttributes can simply
150: * assign the value.
151: *
152: * @param cls the class to check
153: * @return Whether the class represents immutable objects
154: */
155: static boolean isImmutable(final Class<?> cls) {
156: return cls.isPrimitive() || cls.isEnum() || IMMUTABLE_TYPES.contains(cls);
157: }
158:
159: @Override
160: public void mergeChanges(ObjectChangeSet changeSet) {
161: final Object original = changeSet.getChangedObject();
162: try {
163: for (ChangeRecord change : changeSet.getChanges()) {
164: Field f = change.getAttribute().getJavaField();
165: if (isImmutable(f.getType())) {
166: EntityPropertiesUtils.setFieldValue(f, original, change.getNewValue());
167: continue;
168: }
169: Object origVal = EntityPropertiesUtils.getFieldValue(f, original);
170: Object newVal = change.getNewValue();
171: if (newVal == null) {
172: EntityPropertiesUtils.setFieldValue(f, original, null);
173: continue;
174: }
175: getInstanceBuilder(newVal).mergeChanges(f, original, origVal, newVal);
176: }
177: } catch (SecurityException e) {
178: throw new OWLPersistenceException(e);
179: }
180: }
181:
182: private Object getVisitedEntity(Descriptor descriptor, Object original) {
183: assert descriptor != null;
184: assert original != null;
185: return visitedEntities.get(descriptor, original);
186: }
187:
188: private void putVisitedEntity(Descriptor descriptor, Object original, Object clone) {
189: assert descriptor != null;
190: visitedEntities.add(descriptor, original, clone);
191: }
192:
193: AbstractInstanceBuilder getInstanceBuilder(Object toClone) {
194: return builders.getBuilder(toClone);
195: }
196:
197: boolean isTypeManaged(Class<?> cls) {
198: return uow.isTypeManaged(cls);
199: }
200:
201: boolean isOriginalInUoW(Object original) {
202: return uow.containsOriginal(original);
203: }
204:
205: Object getOriginal(Object clone) {
206: return uow.getOriginal(clone);
207: }
208:
209: Metamodel getMetamodel() {
210: return uow.getMetamodel();
211: }
212:
213: @Override
214: public void reset() {
215: visitedEntities.clear();
216: }
217:
218: @Override
219: public void removeVisited(Object instance, Descriptor descriptor) {
220: visitedEntities.remove(descriptor, instance);
221: }
222:
223: IndirectCollection<?> createIndirectCollection(Object c, Object owner, Field f) {
224: return uow.createIndirectCollection(c, owner, f);
225: }
226:
227: public static synchronized boolean isFieldInferred(final Field f) {
228: return f.getAnnotation(Inferred.class) != null;
229: }
230:
231: private static Set<Class<?>> getImmutableTypes() {
232: HashSet<Class<?>> ret = new HashSet<>();
233: ret.add(Boolean.class);
234: ret.add(Character.class);
235: ret.add(Byte.class);
236: ret.add(Short.class);
237: ret.add(Integer.class);
238: ret.add(Long.class);
239: ret.add(Float.class);
240: ret.add(Double.class);
241: ret.add(Void.class);
242: ret.add(String.class);
243: ret.add(URI.class);
244: ret.add(URL.class);
245: return ret;
246: }
247:
248: private final class Builders {
249: private AbstractInstanceBuilder defaultBuilder;
250: private AbstractInstanceBuilder dateBuilder;
251: // Lists and Sets
252: private AbstractInstanceBuilder collectionBuilder;
253: private AbstractInstanceBuilder mapBuilder;
254:
255: private Builders() {
256: this.defaultBuilder = new DefaultInstanceBuilder(CloneBuilderImpl.this, uow);
257: this.dateBuilder = new DateInstanceBuilder(CloneBuilderImpl.this, uow);
258: }
259:
260: private AbstractInstanceBuilder getBuilder(Object toClone) {
261: if (toClone instanceof Date) {
262: return dateBuilder;
263: }
264: if (toClone instanceof Map) {
265: if (mapBuilder == null) {
266: this.mapBuilder = new MapInstanceBuilder(CloneBuilderImpl.this, uow);
267: }
268: return mapBuilder;
269: } else if (toClone instanceof Collection) {
270: if (collectionBuilder == null) {
271: this.collectionBuilder = new CollectionInstanceBuilder(CloneBuilderImpl.this, uow);
272: }
273: return collectionBuilder;
274: } else {
275: return defaultBuilder;
276: }
277: }
278: }
279: }